package bubbleindex;

import com.nativelibs4java.opencl.CLBuffer;
import com.nativelibs4java.opencl.CLEvent;
import com.nativelibs4java.opencl.CLMem;
import static info.yeppp.Core.Add_V32fS32f_V32f;
import static info.yeppp.Core.Add_V64fV64f_V64f;
import static info.yeppp.Core.Multiply_V32fS32f_V32f;
import static info.yeppp.Core.Multiply_V64fS64f_V64f;
import static info.yeppp.Core.Multiply_V64fV64f_V64f;
import static info.yeppp.Math.Cos_V64f_V64f;
import static info.yeppp.Math.Log_V64f_V64f;
import static info.yeppp.Math.Sin_V64f_V64f;
import java.nio.ByteOrder;
import java.util.concurrent.Callable;
import org.apache.commons.math3.util.FastMath;
import org.bridj.Pointer;
import static org.bridj.Pointer.allocateFloats;

/**
 *
 * @author ttrott
 */
public class MyCallable implements Callable<Float> {   
     
    private final int NumberOfDays;
    private final int j;

    static final int Q_SIZE = 18;
    static final int H_SIZE = 19;
    static final int FREQ_SIZE = 70;

    static final float[] Q = new float[Q_SIZE];
    static final float[] H = new float[H_SIZE];
    static final float[] testFreq = new float[FREQ_SIZE];
    
    static final double[] logQi = new double[Q_SIZE];
    static final double[] QiM = new double[Q_SIZE];
    static final double[] cOne = new double[Q_SIZE];
    static final double[] cTwo = new double[Q_SIZE];
    static final double[][] powTempVar = new double[Q_SIZE][H_SIZE];

    static final float Omega_float = BubbleIndex.Omega;
    static final double Omega_double = (double) BubbleIndex.Omega;
    static final float M_float = BubbleIndex.M;
    static final double M_double = (double) BubbleIndex.M;
    
           
    static {
        
        float StartingPoint = (Omega_float / (2.0f * 3.14159f) - 0.2f);
        float Increments = 0.01f;

        float[] tempOne = new float[FREQ_SIZE];
        float[] tempTwo = new float[FREQ_SIZE];
        float[] tempThree = new float[FREQ_SIZE];

        for (int i = 0; i < FREQ_SIZE; i++) {
            tempOne[i] = (float) i;
        }
            
        Multiply_V32fS32f_V32f(tempOne, 0, Increments, tempTwo, 0, FREQ_SIZE);
        Add_V32fS32f_V32f(tempTwo, 0, StartingPoint, tempThree, 0, FREQ_SIZE);

        System.arraycopy(tempThree, 0, testFreq, 0, FREQ_SIZE);

        tempOne = new float[Q_SIZE];
        tempTwo = new float[Q_SIZE];
        tempThree = new float[Q_SIZE];

        StartingPoint = 0.1f;
        Increments = 0.05f;

        for (int i = 0; i < Q_SIZE; i++) {
            tempOne[i] = (float) i;
        }

        Multiply_V32fS32f_V32f(tempOne, 0, Increments, tempTwo, 0, Q_SIZE);
        Add_V32fS32f_V32f(tempTwo, 0, StartingPoint, tempThree, 0, Q_SIZE);
    
        System.arraycopy(tempThree, 0, Q, 0, Q_SIZE);

        tempOne = new float[H_SIZE];
        tempTwo = new float[H_SIZE];
        tempThree = new float[H_SIZE];

        StartingPoint = -1.0f * 0.9f;
        Increments = 0.1f;
        for (int i = 0; i < H_SIZE; i++) {
            tempOne[i] = (float) i;
        }        

        Multiply_V32fS32f_V32f(tempOne, 0, Increments, tempTwo, 0, Q_SIZE);
        Add_V32fS32f_V32f(tempTwo, 0, StartingPoint, tempThree, 0, Q_SIZE);
            
        System.arraycopy(tempThree, 0, H, 0, H_SIZE);
        
        for (int i = 0; i < Q_SIZE; i++) {        
            logQi[i] = FastMath.log(Q[i]) * Omega_double;
            QiM[i] = FastMath.pow(Q[i], M_double);
            cOne[i] = 1.0 - QiM[i] * FastMath.cos(logQi[i]);
            cTwo[i] = FastMath.sin(logQi[i]);

        }
        
        for (int i = 0; i < Q_SIZE; i++) {
            for (int j = 0; j < H_SIZE; j++) {
                powTempVar[i][j] = FastMath.pow((1.0 - Q[i]), H[j]);
            }
        }   
    }
    
    public MyCallable (int index, final int number) {
        NumberOfDays = number;
        j = index;       
    }
    
    /**
     * This is a callable task to calculate the HQ derivative value of a 
     * given time series.
     * @return The HQ Derivative value, or The Value of the Bubble Index 
     */
    
    @Override
    public Float call() {
                
        if (!BuildIndex.Stop) {
            
            ByteOrder byteOrder = BuildIndex.byteOrder;
            
            Pointer<Float> Values = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            Pointer<Float> MeanArray = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            Pointer<Float> timevalues = allocateFloats(NumberOfDays).order(byteOrder);
            Pointer<Float> logtimeValues = allocateFloats(NumberOfDays).order(byteOrder);
            Pointer<Float> testfreq = allocateFloats(FREQ_SIZE).order(byteOrder);
            Pointer<Float> q = allocateFloats(Q_SIZE).order(byteOrder);
            Pointer<Float> h = allocateFloats(H_SIZE).order(byteOrder);
            Pointer<Float> hqderiv = allocateFloats(NumberOfDays * (Q_SIZE * H_SIZE + 1)).order(byteOrder);

            for (int i = 0; i < FREQ_SIZE; i++) {
                testfreq.set(i, testFreq[i]);
            }

            for (int i = 0; i < Q_SIZE; i++) {
                q.set(i, Q[i]);
            }
            
            for (int i = 0; i < H_SIZE; i++) {
                h.set(i, H[i]);
            }      

            double[] TimeValues = new double[NumberOfDays];
            double[] TimeValues_M_Power = new double[NumberOfDays];
            double[] LogCosTimeValues = new double[NumberOfDays];
            double[] SelectedData = new double[NumberOfDays];
            double[] Coef = new double[3];
            double[] LogTimeValuesTemp = new double[NumberOfDays];

            //Arrays used in the H,Q derivative calculation and periodogram
            for (int k = 0; k < NumberOfDays; k++) {
                TimeValues[k] = NumberOfDays + BubbleIndex.T_crit - k;
                TimeValues_M_Power[k] = FastMath.pow(TimeValues[k], M_float);
                SelectedData[k] = BubbleIndex.DailyPriceValues[k + j + 1];
            }
            double[] tempFour = new double[NumberOfDays];
            double[] tempFive = new double[NumberOfDays];

            Log_V64f_V64f(TimeValues, 0, tempFour, 0, NumberOfDays);
            
            System.arraycopy(tempFour, 0, LogTimeValuesTemp, 0, NumberOfDays);
            
            Multiply_V64fS64f_V64f(tempFour, 0, Omega_double, tempFive, 0, NumberOfDays);
            Cos_V64f_V64f(tempFive, 0, tempFour, 0, NumberOfDays);
            Multiply_V64fV64f_V64f(tempFour, 0, TimeValues_M_Power, 0, tempFive, 0, NumberOfDays);

            System.arraycopy(tempFive, 0, LogCosTimeValues, 0, NumberOfDays);
            
            //Normalize data to a price starting at 100
            BuildIndex.Normalize(SelectedData, NumberOfDays);

            BuildIndex.DataReverse(SelectedData, NumberOfDays);

            //Fit the curve with the equation given in:
            BuildIndex.LinearFit(SelectedData, TimeValues_M_Power, LogCosTimeValues,
                        Coef, NumberOfDays);
            
            tempFour = new double[NumberOfDays];
            tempFive = new double[NumberOfDays];
            double[] tempSix = new double[NumberOfDays];

            
            Multiply_V64fS64f_V64f(LogTimeValuesTemp, 0, Omega_double, tempFour, 0, NumberOfDays);

            Cos_V64f_V64f(tempFour, 0, tempFive, 0, NumberOfDays);
            Sin_V64f_V64f(tempFour, 0, tempSix, 0, NumberOfDays);
            
            double[] mean = new double[Q_SIZE * H_SIZE];
            
            for (int i = 0; i < Q_SIZE; i++) {
                
                double Qi_M = QiM[i];
                double C_one = cOne[i];
                double C_two = cTwo[i];
                double[] tempSeven = new double[NumberOfDays];
                double[] tempEight = new double[NumberOfDays];
                double[] tempNine = new double[NumberOfDays];
                Multiply_V64fS64f_V64f(tempFive, 0, C_one, tempSeven, 0, NumberOfDays);
                Multiply_V64fS64f_V64f(tempSix, 0, C_two, tempEight, 0, NumberOfDays);
                Add_V64fV64f_V64f(tempSeven, 0, tempEight, 0, tempNine, 0, NumberOfDays);
               
                for (int p = 0; p < H_SIZE; p++) {
                    
                    double tempVar = powTempVar[i][p];
                    double B_prime, C_prime;

                    
                    //Took out the negative sign for B_Prime
                    B_prime = 1.0 * (Coef[1]) * (1.0 - Qi_M) * 1.0 / tempVar;

                    C_prime = (Coef[2]) * 1.0 / tempVar;
                    
                    double sum = 0.0f;
                    
                    for (int k = 0; k < NumberOfDays; k++) {

                        double G_function = tempNine[k];
                        
                        double tempValue = (FastMath.pow(TimeValues[k], 
                                M_double - H[p])) *
                                (B_prime + C_prime * G_function);
                        sum = sum + tempValue;
                        hqderiv.set((i + p * Q_SIZE) * NumberOfDays + k, (float)tempValue);
                    }
                    
                    mean[i + p * Q_SIZE] = sum * 1.0f / NumberOfDays;
                }
                
            }  
            
            tempFour = null;
            tempFive = null;
            tempSix = null;
            
            //Prepare the arrays to be copied to device memory
            for (int i = 0; i < NumberOfDays; i++) {
                timevalues.set(i, (float) TimeValues[i]);
                logtimeValues.set(i, (float) LogTimeValuesTemp[i]);
            }
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                Values.set(i, 0.0f);
                MeanArray.set(i, (float)mean[i]);
            }       

            CLBuffer<Float> 
                values = BuildIndex.context.createFloatBuffer(CLMem.Usage.Output, Values),
                meanArray = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, MeanArray),
                LogTimeValues = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, logtimeValues),
                TimeValuesFinal = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, timevalues),                
                TestFrequencies = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, testfreq),
                Q2 = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, q),
                H2 = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, h),
                HQDerivativeData = BuildIndex.context.createFloatBuffer(CLMem.Usage.Input, hqderiv);
            
            Values.release();
            MeanArray.release();
            logtimeValues.release();
            timevalues.release();
            testfreq.release();
            q.release();
            h.release();
            hqderiv.release();

            float Temp = 0.0f;
            final float output;

            Pointer<Float> outPtr = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                outPtr.set(i, 0.0f);
            }
            
            CLEvent addEvt;
          //  synchronized (claddFloatsKernel) {
            synchronized (BuildIndex.addFloatsKernel) {
       
                BuildIndex.addFloatsKernel.setArg(0, values);
                BuildIndex.addFloatsKernel.setArg(1, meanArray);
                BuildIndex.addFloatsKernel.setArg(2, LogTimeValues);
                BuildIndex.addFloatsKernel.setArg(3, TimeValuesFinal);
                BuildIndex.addFloatsKernel.setArg(4, TestFrequencies);
                BuildIndex.addFloatsKernel.setArg(5, HQDerivativeData);
                BuildIndex.addFloatsKernel.setArg(6, Q2);
                BuildIndex.addFloatsKernel.setArg(7, H2);
                BuildIndex.addFloatsKernel.setArg(8, M_float);
                BuildIndex.addFloatsKernel.setArg(9, Omega_float);
                BuildIndex.addFloatsKernel.setArg(10, (float)Coef[1]);
                BuildIndex.addFloatsKernel.setArg(11, (float)Coef[2]);
                BuildIndex.addFloatsKernel.setArg(12, Q_SIZE);
                BuildIndex.addFloatsKernel.setArg(13, H_SIZE);
                BuildIndex.addFloatsKernel.setArg(14, FREQ_SIZE);
                BuildIndex.addFloatsKernel.setArg(15, NumberOfDays);

	        int[] globalSizes = new int[] {Q_SIZE , H_SIZE};
	        addEvt = BuildIndex.addFloatsKernel.enqueueNDRange(BuildIndex.queue, globalSizes);
              
            }
           
            outPtr = values.read(BuildIndex.queue, addEvt); 

            values.release();
            meanArray.release();
            LogTimeValues.release();
            TimeValuesFinal.release();
            HQDerivativeData.release();
            TestFrequencies.release();
            Q2.release();
            H2.release();                
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                if (outPtr.get(i) > Temp) {
                    Temp = outPtr.get(i);
                }
            }
            
            outPtr.release();
            output = Temp;
            
            final int DisplayPeriod = j + NumberOfDays;
            final int extra = NumberOfDays;
            final String name = BubbleIndex.SelectionName;
            final String DisplayPeriodString = BubbleIndex.DailyPriceDateList.get(DisplayPeriod);
            BubbleIndex.displayOutput("Name: " + name + " Date: " + DisplayPeriodString +
                            " Value: " + output + " Length: " + extra, false);
            
            TimeValues = null;
            TimeValues_M_Power = null;
            LogCosTimeValues = null;
            SelectedData = null;
            Coef = null;
            LogTimeValuesTemp = null;
            mean = null;
            addEvt.release();
            
            return Temp;                
        }
                
        else {            
            return 0.0f;        
        }
    }
}
  
   